﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

using LowLevelGraphics.Histograms;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// This calculates an entropy and uses it for thresholding
    /// </summary>
    public class EntropyThreshold : BaseForeBackgroundColorFilter
    {
        protected GrayScale m_GrayScale = new GrayScale();
        /// <summary>
        /// Default constructor
        /// </summary>
        public EntropyThreshold()
        {
        }

        /// <summary>
        /// use this to create a deep copy
        /// </summary>
        /// <param name="_entropyThreshold"></param>
        internal EntropyThreshold(EntropyThreshold _entropyThreshold)
        {
        }

        /// <summary>
        /// Execute Filter
        /// </summary>
        /// <param name="_bitmap"></param>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            m_GrayScale.Execute(_bitmap);
            Statistics histogram = new Statistics(_bitmap);

            int[] hist = new int[histogram.Count];
            for (int i = 0; i < histogram.Count; i++)
            {
                StatisticValue sValue = histogram[i];
                switch (M_PROCESSING_MODE)
                {
                    case PROCESSING_MODE.B:
                        hist[i] = sValue.B;
                        break;

                    case PROCESSING_MODE.A:
                        hist[i] = sValue.A;
                        break;

                    case PROCESSING_MODE.Brightness:
                        hist[i] = (int)sValue.Brightness * 255;
                        break;

                    default:
                    case PROCESSING_MODE.Color:
                        hist[i] = sValue.ColorValue;
                        break;

                    case PROCESSING_MODE.ColorCount:
                        hist[i] = histogram.ColorDictionary[sValue.Color];
                        break;

                    case PROCESSING_MODE.G:
                        hist[i] = sValue.G;
                        break;

                    case PROCESSING_MODE.GrayColor:
                        hist[i] = ColorFunctions.Gray(sValue.Color);
                        break;

                    case PROCESSING_MODE.GrayValue:
                        hist[i] = sValue.GrayValue;
                        break;

                    case PROCESSING_MODE.Hue:
                        hist[i] = (int)sValue.Hue * 255;
                        break;

                    case PROCESSING_MODE.Luminance:
                        hist[i] = (int)sValue.Luminance * 255;
                        break;
                }
            }
            int nWidth = _bitmap.Width;
            int nHeight = _bitmap.Height;
            int x = 0;
            int y = 0;
            int threshold = EntropySplit(hist);
            int nColor = 0;

            for (y = 0; y < nHeight; y++)
            {
                for (x = 0; x < nWidth; x++)
                {
                    nColor = _bitmap.GetGrayPixelValue(x, y);
                    _bitmap.SetPixel(x, y, nColor < threshold ? m_ColorBackground : m_ColorForeground);
                }
            }
            return _bitmap;
        }

        //TODO: EntropyThreshold: speedup, dramatically
        /// <summary>
        /// 
        /// </summary>
        /// <param name="hist"></param>
        /// <returns></returns>
        private int EntropySplit(int[] hist)
        {
            // Normalize histogram, that is makes the sum of all bins equal to 1.
            double sum = 0;
            for (int i = 0; i < hist.Length; ++i)
            {
                sum += hist[i];
            }
            if (sum == 0)
            {
                sum = 1;
                // This should not normally happen, but...
                //throw new ArgumentException("Empty histogram: sum of all bins is zero.");
            }

            double[] normalizedHist = new double[hist.Length];
            for (int i = 0; i < hist.Length; i++)
            {
                normalizedHist[i] = hist[i] / sum;
            }

            //
            double[] pT = new double[hist.Length];
            pT[0] = normalizedHist[0];
            for (int i = 1; i < hist.Length; i++)
            {
                pT[i] = pT[i - 1] + normalizedHist[i];
            }

            // Entropy for black and white parts of the histogram
            double dEpsilon = Double.MinValue;
            double[] hB = new double[hist.Length];
            double[] hW = new double[hist.Length];

            for (int t = 0; t < hist.Length; t++)
            {
                // Black entropy
                if (pT[t] > dEpsilon)
                {
                    double hhB = 0;
                    for (int i = 0; i <= t; i++)
                    {
                        if (normalizedHist[i] > dEpsilon)
                        {
                            hhB -= normalizedHist[i] / pT[t] * Math.Log(normalizedHist[i] / pT[t]);
                        }
                    }
                    hB[t] = hhB;
                }
                else
                {
                    hB[t] = 0;
                }

                // White  entropy
                double pTW = 1 - pT[t];
                if (pTW > dEpsilon)
                {
                    double hhW = 0;
                    for (int i = t + 1; i < hist.Length; ++i)
                    {
                        if (normalizedHist[i] > dEpsilon)
                        {
                            hhW -= normalizedHist[i] / pTW * Math.Log(normalizedHist[i] / pTW);
                        }
                    }
                    hW[t] = hhW;
                }
                else
                {
                    hW[t] = 0;
                }
            }

            // Find histogram index with maximum entropy
            double jMax = hB[0] + hW[0];
            int tMax = 0;
            for (int t = 1; t < hist.Length; ++t)
            {
                double j = hB[t] + hW[t];
                if (j > jMax)
                {
                    jMax = j;
                    tMax = t;
                }
            }

            return tMax;
        }

        /// <summary>
        /// Use this to clone the filter
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new EntropyThreshold(this);
        }
    }
}
